#ifndef TALK_P2P_BASE_STUNPORT_H_
#define TALK_P2P_BASE_STUNPORT_H_

#include <string>

#include "base/asyncpacketsocket.h"
#include "p2p/port.h"
#include "p2p/stunrequest.h"

// TODO(mallinath) - Rename stunport.cc|h to udpport.cc|h.
namespace base {
	class AsyncResolver;
	class SignalThread;
}

namespace cricket {

	// Communicates using the address on the outside of a NAT.
	class UDPPort : public Port {
	public:
		static UDPPort* Create(base::Thread* thread,
			base::Network* network,
			base::AsyncPacketSocket* socket,
			const std::string& username,
			const std::string& password) {
				UDPPort* port = new UDPPort(thread, network, socket, username, password);
				if (!port->Init()) {
					delete port;
					port = NULL;
				}
				return port;
		}

		static UDPPort* Create(base::Thread* thread,
			base::PacketSocketFactory* factory,
			base::Network* network,
			const base::IPAddress& ip,
			int min_port, int max_port,
			const std::string& username,
			const std::string& password) {
				UDPPort* port = new UDPPort(thread, factory, network,
					ip, min_port, max_port,
					username, password);
				if (!port->Init()) {
					delete port;
					port = NULL;
				}
				return port;
		}
		virtual ~UDPPort();

		base::SocketAddress GetLocalAddress() const {
			return socket_->GetLocalAddress();
		}

		const base::SocketAddress& server_addr() const { return server_addr_; }
		void set_server_addr(const base::SocketAddress& addr) {
			server_addr_ = addr;
		}

		virtual void PrepareAddress();

		virtual Connection* CreateConnection(const Candidate& address,
			CandidateOrigin origin);
		virtual int SetOption(base::Socket::Option opt, int value);
		virtual int GetOption(base::Socket::Option opt, int* value);
		virtual int GetError();

		virtual bool HandleIncomingPacket(
			base::AsyncPacketSocket* socket, const char* data, size_t size,
			const base::SocketAddress& remote_addr) {
				// All packets given to UDP port will be consumed.
				OnReadPacket(socket, data, size, remote_addr);
				return true;
		}

		void set_stun_keepalive_delay(int delay) {
			stun_keepalive_delay_ = delay;
		}
		int stun_keepalive_delay() const {
			return stun_keepalive_delay_;
		}

	protected:
		UDPPort(base::Thread* thread, base::PacketSocketFactory* factory,
			base::Network* network, const base::IPAddress& ip,
			int min_port, int max_port,
			const std::string& username, const std::string& password);

		UDPPort(base::Thread* thread, base::Network* network,
			base::AsyncPacketSocket* socket,
			const std::string& username, const std::string& password);

		bool Init();

		virtual int SendTo(const void* data, size_t size,
			const base::SocketAddress& addr, bool payload);

		void OnLocalAddressReady(base::AsyncPacketSocket* socket,
			const base::SocketAddress& address);
		void OnReadPacket(base::AsyncPacketSocket* socket,
			const char* data, size_t size,
			const base::SocketAddress& remote_addr);
		void OnReadyToSend(base::AsyncPacketSocket* socket);

		// This method will send STUN binding request if STUN server address is set.
		void MaybePrepareStunCandidate();

		void SendStunBindingRequest();


	private:
		// DNS resolution of the STUN server.
		void ResolveStunAddress();
		void OnResolveResult(base::SignalThread* thread);

		// Below methods handles binding request responses.
		void OnStunBindingRequestSucceeded(const base::SocketAddress& stun_addr);
		void OnStunBindingOrResolveRequestFailed();

		// Sends STUN requests to the server.
		void OnSendPacket(const void* data, size_t size, StunRequest* req);

		// TODO(mallinaht) - Move this up to cricket::Port when SignalAddressReady is
		// changed to SignalPortReady.
		void SetResult(bool success);

		base::SocketAddress server_addr_;
		StunRequestManager requests_;
		base::AsyncPacketSocket* socket_;
		int error_;
		base::AsyncResolver* resolver_;
		bool ready_;
		int stun_keepalive_delay_;

		friend class StunBindingRequest;
	};

	class StunPort : public UDPPort {
	public:
		static StunPort* Create(base::Thread* thread,
			base::PacketSocketFactory* factory,
			base::Network* network,
			const base::IPAddress& ip,
			int min_port, int max_port,
			const std::string& username,
			const std::string& password,
			const base::SocketAddress& server_addr) {
				StunPort* port = new StunPort(thread, factory, network,
					ip, min_port, max_port,
					username, password, server_addr);
				if (!port->Init()) {
					delete port;
					port = NULL;
				}
				return port;
		}

		virtual ~StunPort() {}

		virtual void PrepareAddress() {
			SendStunBindingRequest();
		}

	protected:
		StunPort(base::Thread* thread, base::PacketSocketFactory* factory,
			base::Network* network, const base::IPAddress& ip,
			int min_port, int max_port,
			const std::string& username, const std::string& password,
			const base::SocketAddress& server_address)
			: UDPPort(thread, factory, network, ip, min_port, max_port, username,
			password) {
				// UDPPort will set these to local udp, updating these to STUN.
				set_type(STUN_PORT_TYPE);
				set_server_addr(server_address);
		}
	};

}  // namespace cricket

#endif  // TALK_P2P_BASE_STUNPORT_H_
